#ifndef __MATRIX__H__
#define __MATRIX__H__
#include "vector.cpp"
#include "outOfRangeException.cpp"
#include <iostream>

/*
	Classe colonne pour gérer l'accès aux tableaux avec test et retour d'erreur la cas échéant
*/
template <class T> class Column{
	private:
		unsigned int size;
		T* column;
	public:
		Column();
		Column(unsigned int size);
		Column(const Column &c);
		~Column();
		Column<T>& operator=(const Column &m);
		T& operator[](unsigned int col) const;
		const unsigned int& getSize() const;
};

template <class T> class Matrix{
	private:
		unsigned int sizeRow, sizeCol;
		Column<T> *row;
	public:
		Matrix();
		Matrix(unsigned int numRow_,unsigned int numCol_);
		Matrix(const Matrix &m);
		~Matrix();
		static Matrix MatrixIdent(unsigned int numRow_,unsigned int numCol_);
		static Matrix MatrixDiag(unsigned int numRow_,unsigned int numCol_);	//Identique à la matrice Ident
		static Matrix MatrixRotateEuler(vector<T> v);
		static Matrix MatrixRotateX(double x);
		static Matrix MatrixRotateY(double y);
		static Matrix MatrixRotateZ(double z);
		static Matrix MatrixScale(vector<T> v);	

		static Matrix projection(double z);
		static Matrix MatrixTranslation(double x,double y,double z);		///Ne pas utiliser à corriger.

		Matrix<T>& operator=(const Matrix &m);
		Matrix<T> operator*(const Matrix &m) const;
		void operator*=(const int &t);
		void operator*=(const Matrix &m);			//Ne pas utiliser à corriger.
		Matrix<T> operator+(const Matrix &m) const;
		void operator+=(const Matrix &m);

		Column<T>& operator[](unsigned int row) const;
		const unsigned int& getNumCol() const;
		const unsigned int& getNumRow() const;
		void print() const;
};

template <class T>
Matrix<T>::Matrix(){
	sizeRow = sizeCol = 0;
	row = NULL;
}

template <class T>
Matrix<T>::Matrix(unsigned int numRow_,unsigned int numCol_){
	sizeRow = numRow_;
	sizeCol = numCol_;
	row = new Column<T>[sizeRow];
	
	for(unsigned int i=0;i<sizeRow;i++){
		row[i] = Column<T>(sizeCol);
	}
}

template <class T>
Matrix<T>::Matrix(const Matrix &m){
	(*this) = m;
}

template <class T>
Matrix<T>::~Matrix(){
	if(row){
		delete[] row;
		sizeRow = 0;
		sizeCol = 0;
	}
}

template <class T> 
Matrix<T> Matrix<T>::MatrixIdent(unsigned int numRow_,unsigned int numCol_){
	Matrix<T> m = Matrix(numRow_,numCol_);
	
	for(unsigned int i=0;i<m.sizeRow && i<m.sizeCol;i++){
		m[i][i] = T(1.);
	}
	return m;
}

template <class T> 
Matrix<T> Matrix<T>::MatrixDiag(unsigned int numRow_,unsigned int numCol_){
	Matrix<T> m = Matrix(numRow_,numCol_);
	
	for(unsigned int i=0;i<m.sizeRow && i<m.sizeCol;i++){
		m[i][i] = T(1.);
	}

	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixRotateEuler(vector<T> v){
	T A,B,C,D,E,F,AD,BD;

	A = (T)cos(v.x);
	B = (T)sin(v.x);
	C = (T)cos(v.y);
	D = (T)sin(v.y);
	E = (T)cos(v.z);
	F = (T)sin(v.z);

	AD = A*D;
	BD = B*D;

	Matrix<T> m(4,4);
	
	m[0][0] = C*E;
	m[0][1] = -C*F;
	m[0][2] = -D;
	m[0][3] = T(0.);

	m[1][0] = -BD*E+A*F;
	m[1][1] = -BD*F+A*E;
	m[1][2] =-B*C;
	m[1][3] = T(0.);

	m[2][0] = AD*E+B*F;
	m[2][1] = -AD*F+B*E;
	m[2][2] = A*C;
	m[2][3] = T(0.);

	m[3][0] = T(0.);
	m[3][1] = T(0.);
	m[3][2] = T(0.);
	m[3][3] = T(1.);

	return m;
}


template <class T>
Matrix<T> Matrix<T>::MatrixRotateX(double x){
	Matrix<T> m(4,4);
	double c,s;
	c = cos(x);
	s = sin(x);

	m[0][0] = T(1.);
	m[3][3] = T(1.);

	m[1][1] = c;
	m[1][2] = -s;
	m[2][1] = s;
	m[2][2] = c;

	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixRotateY(double y){
	Matrix<T> m(4,4);
	double c,s;
	c = cos(y);
	s = sin(y);

	m[0][0] = c;
	m[0][2] = -s;

	m[1][1] = T(1.);

	m[2][0] = s;
	m[2][2] = c;

	m[3][3] = T(1.);

	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixRotateZ(double z){
	Matrix<T> m(4,4);
	double c,s;
	c = cos(z);
	s = sin(z);

	m[0][0] = c;
	m[0][1] = -s;

	m[1][0] = s;
	m[1][1] = c;

	m[2][2] = T(1.);

	m[3][3] = T(1.);

	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixScale(vector<T> v){
	Matrix<T> m(4,4);
	
	m[0][0]= v.x;
	m[1][1]= v.y;
	m[2][2]= v.z;
	m[3][3]= T(1.);

	return m;
}

template <class T> 
Matrix<T> Matrix<T>::MatrixTranslation(double x,double y,double z){
	Matrix<T> m = MatrixDiag(4,4);
	

	m[0][3] = x;
	m[1][3] = y;
	m[2][3] = z;
	m.print();
	return m;
}

template <class T>
const unsigned int& Matrix<T>::getNumRow() const{
	return sizeRow;
}

template <class T>
const unsigned int& Matrix<T>::getNumCol() const{
	return sizeCol;
}

template <class T>
Matrix<T>& Matrix<T>::operator=(const Matrix &m){
	unsigned int i,j;

	if(m.row){
		if(row){
			if(sizeRow != m.sizeRow ){
				delete[] row;
				row = new Column<T>[m.sizeRow];
				for(int unsigned i=0;i<m.sizeRow;i++){
					row[i] = Column<T>(m.sizeCol);
				}
			}
			else{
				if(sizeCol != m.sizeCol ){
					for(int unsigned i=0;i<m.sizeRow;i++){
						row[i] = Column<T>(m.sizeCol);
					}
				}
				else{
					//use less
				}
			}
		}
		else{
			row = new Column<T>[m.sizeRow];
			for(int unsigned i=0;i<m.sizeRow;i++){
				row[i] = Column<T>(m.sizeCol);
			}
		}
	}
	else{
		delete[] row;
	}

	//Changement taille
	sizeRow = m.sizeRow;
	sizeCol = m.sizeCol;

	//Remplissage
	for(i=0;i<sizeRow;i++){
		for(j=0;j<sizeCol;j++){
			row[i][j] = m[i][j];
		}
	}

	return *this;
}

template <class T>
Matrix<T> Matrix<T>::operator*(const Matrix &m) const{
	Matrix r = Matrix(m.sizeRow,m.sizeCol);
	for(unsigned int i=0;i<sizeRow;i++){
		for(unsigned int j=0;j<m.sizeCol;j++){
			r[i][j]=0;
			for(unsigned int k=0;k<m.sizeRow;k++){
				r[i][j] += row[i][k] * m[k][j];
			}
		}
	}
	return r;
}

template <class T>
void Matrix<T>::operator*=(const int &t){
	for(unsigned int i=0;i<sizeRow;i++){
		for(unsigned int j=0;j<sizeCol;j++){
			row[i][j] = row[i][j]*t;	
		}
	}
}

template <class T>
void Matrix<T>::operator*=(const Matrix &m){
	T x;
	for(unsigned int i=0;i<m.sizeRow;i++){
		for(unsigned int j=0;j<sizeCol;j++){
			x = 0;
			for(unsigned int k=0;k<sizeRow;k++){
				x += m[i][k] * row[k][j];
			}
			row[i][j] = x;	
		}
	}
}

template <class T>
Matrix<T> Matrix<T>::operator+(const Matrix &m) const{
	Matrix r = Matrix(m.numRow,m.numCol);
	for(unsigned int i=0;i<sizeRow;i++){
		for(unsigned int j=0;j<sizeCol;j++){
			r[i][j]= row[i][j] + m[i][j];
		}
	}
	return r;
}

template <class T>
void Matrix<T>::operator+=(const Matrix &m){
	for(unsigned int i=0;i<sizeRow;i++){
		for(unsigned int j=0;j<sizeCol;j++){
			row[i][j] += m[i][j];	
		}
	}
}

template <class T>
Column<T>& Matrix<T>::operator[](unsigned int row) const{
	if(row>=sizeRow){
		throw OutOfRangeException("Out of range");
	}
	return this->row[row];
}

template <class T>
void Matrix<T>::print() const{
	if(row){
		for(unsigned int i=0;i<sizeRow;i++){
			for(unsigned int j=0;j<sizeCol;j++){
				std::cout<<" "<<row[i][j];
			}
			std::cout<<std::endl;
		}
	}
}


template <class T>
Column<T>::Column(){
	size=0;
	column = NULL;
}

template <class T>
Column<T>::Column(unsigned int size){
	this->size = size;
	column = new T[size];

	for(unsigned int i=0;i<size;i++){
		column[i] = T(0.);
	}
}

template <class T>
Column<T>::Column(const Column &m){
	(*this) = m;
}

template <class T>
Column<T>::~Column(){
	if(column){
		delete[] column;
		column = NULL;
	}
}

template <class T>
const unsigned int& Column<T>::getSize() const{
	return size;
}

template <class T>
Column<T>& Column<T>::operator=(const Column &c){
	
	if(c.column){
		if(column){
			if(size != c.size){
				delete[] column;
				column = new T[c.size];
			}
			else{
				//Yeah useless
			}
		}
		else{
			column = new T[c.size];
		}
	}
	else{
		if(column){
			delete[] column;
		}
		else{
			//Yeah useless
		}
	}

	//Changement taille
	size = c.size;

	//Remplissage
	for(unsigned int i=0;i<size;i++){
		column[i] = c.column[i];
	}

	return *this;
}

template <class T>
T& Column<T>::operator[](unsigned int col) const{
	if(col>=size){
		throw OutOfRangeException("Out of range");
	}
	return column[col];
}

#endif
